package com.example.zz.example.pattern.singleton;

/**
 * Created by zhangzhuo.
 * Blog: https://blog.csdn.net/zhangzhuo1024
 * Date: 2020/4/30
 *
 * 懒汉式单例多线程存在的问题：
 * 问题一：
 * 如下懒汉式单例，当线程一判断sInstance == null，进入判断条件里面进行初始化但初始化还未完成时，线程二进入判断，
 * 此时sInstance还未被赋值，所以线程二也可以进行自己的初始化，这样线程一和线程二最终会获得不同的对象，就不是单例了，
 * 他们分别使用成员变量时，获取的也不是同一个值了，是不同对象的值，此时出现的是功能异常，但是不会出现崩溃
 *
 * 问题一解决：
 * 所以我们在判断条件外添加锁，保证每次只有一个线程进入并初始化，如：getsInstance2()，此时线程一持锁进行初始化，
 * 线程二如果访问，就必须等线程一结束，就不存在问题一了，但是这样每次访问都会持锁，会损耗性能，
 * 所以在锁外面再添加一次判断避免每次都持锁，如：getsInstance3()
 *
 * 问题二：
 * getsInstance3()在超高并发的情况下运行时，可能会出现崩溃，原因是：java文件会被编译成.class字节码文件，字节码文件最终编译为汇编指令的时候，
 * 为提高性能，编译器和处理器都会对编译后的指令进行重排序，sInstance = new LazySingle(); 这行代码在编译为指令时分三步，1、申请内存空间、
 * 2、初始化对象，3、将sInstance指向申请的内存地址。其中第二步和第三步是没有依赖的，会存在重排序，所以当线程一重排序为1、3、2时，
 * 先将sInstance指向申请的内存地址，再初始化对象，但是对象初始化还没成功时，线程二访问getsInstance3，此时sInstance判断不为空，直接使用sInstance
 * 去调用时，会崩溃。
 *
 * 问题二解决：
 * sInstance使用volatile进行修饰。最终结果参考LockLazySingle.java类中的实现
 * volatile：1、保证可见性，2、禁止指令重拍
 * 当一个变量使用volatile修饰的时候，volatile关键字就是内存屏障。当编译器在生成指令顺序的时候，发现了volatile，就直接忽略掉，
 * 不再重排序了，就没有重排序问题了。
 *
 */
public class LazySingle {
    private static LazySingle sInstance = null;

    private LazySingle() {

    }

    public static LazySingle getsInstance() {
        if (sInstance == null) {
            sInstance = new LazySingle();
        }
        return sInstance;
    }

    public static LazySingle getsInstance2() {
        synchronized (LazySingle.class) {
            if (sInstance == null) {
                sInstance = new LazySingle();
            }
        }
        return sInstance;
    }

    public static LazySingle getsInstance3() {
        if (sInstance == null) {
            synchronized (LazySingle.class) {
                if (sInstance == null) {
                    sInstance = new LazySingle();
                }
            }
        }
        return sInstance;
    }

    public void doSomething() {

    }
}
